package jiggle;

import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;

/* Abstract base class for all JIGGLE objects that have rectangular
representations.  Known subclasses: Vertex, EdgeLabel, QuadTree. */

public abstract class Cell extends JiggleObject {
    // TODO: move this to Vertex
    public Object data = null;

	private int dimensions = 2; /* default is a 2-D cell */
	private double weight; /* weight of cell */
	private double coords []; /* coordinates of center of cell */
	private double min [], max []; /* bounding box of cell */
	private double size []; /* dimensions of cell */

	public int gradientNdx = -1;
    
    
	protected Cell () {setDimensions (2); weight = 0;}

	public double getWeight () {return weight;}
	public void setWeight (double w) {weight = w;}

	public int getDimensions () {return dimensions;}
	public void setDimensions (int d) {
		dimensions = d; coords = new double [d]; size = new double [d];
		min = new double [d]; max = new double [d];
	}
	
	public boolean isValid() {
	    if (Double.isNaN(weight))			return false;
	    for (int i=0;i<dimensions;i++) {
	        if (Double.isNaN(coords[i])) 	return false;
	        if (Double.isNaN(min[i])) 		return false;
	        if (Double.isNaN(max[i])) 		return false;
	        if (Double.isNaN(size[i])) 		return false;
	    }
	    return true;
	}

	public double [] getCoords () {return coords;}
	public void setCoords (double [] c) {
		for (int i = 0; i < dimensions; i++) 
		    coords [i] = c [i];
	}

	public double [] getMin () {return min;}
	void setMin (double [] c) {
		for (int i = 0; i < dimensions; i++) min [i] = c [i];
		recomputeSize ();
	}

	public double [] getMax () {return max;}
	void setMax (double [] c) {
		for (int i = 0; i < dimensions; i++) max [i] = c [i];
		recomputeSize ();
	}

	public void recomputeSize () {
		for (int i = 0; i < dimensions; i++) size [i] = max [i] - min [i];
	}

	public double [] getSize () {return size;}
	public void setSize (double [] c) {
		for (int i = 0; i < dimensions; i++) size [i] = c [i];
		recomputeBoundaries ();
	}
    public void setSize(int width, int height) {
		size [0] = width;
		size [1] = height;
		recomputeBoundaries ();
    }


    public void recomputeBoundaries () {
		for (int i = 0; i < dimensions; i++) {
			min [i] = coords [i] - size [i] / 2;
			max [i] = coords [i] + size [i] / 2;
		}
	}

    public void recomputeCoords () {
		for (int i = 0; i < dimensions; i++) {
            coords[i] = min[i] + size[i] / 2;
        }
	}
    
    final double largeScalar = 100;
    final double largeCoord = 100*100;

    boolean translate (double [] vector) { return translate (1, vector);}
	boolean translate (double scalar, double [] vector) {
        // for callers:
        //  scalar == step size
	    //  vector == descentDirection
        // for us, we just need to translate by scalar*vector
        
	    boolean success = true;
	    
	    // tame down translation (scalar*vector)
		for (int i = 0; i < dimensions; i++) {
		    if (Math.abs(scalar * vector[i]) > largeCoord) scalar = Math.min(largeCoord/Math.abs(vector[i]), scalar);
		}
		
		for (int i = 0; i < dimensions; i++) {
			double translation = scalar * vector [i];
			coords [i] += translation;
			min [i] += translation;
			max [i] += translation;
		    // TODO: patching should not be happening anymore 
			// (especially given the previous scaling down of translations)
			if (Math.abs(coords[i]) > largeCoord) {			// PATCHWORK
			    // scramble
			    System.err.println("ERR: PATCHING COORD: " + gradientNdx + "[" + i + "], " + 
			            "coord:" + coords[i] + ", " + 
			            scalar + " x " +
                        vector[i]);
			    //coords[i] = Math.random() * largeCoord;
                coords[i] = coords[i] * largeCoord/Math.abs(coords[i]);
			    recomputeBoundaries();
			    success = false;
			    continue;
			}
			if (Math.abs(coords[i]) == Double.POSITIVE_INFINITY) {
			    System.err.println("ERR: " + scalar + " " + vector[i]);
			    throw new RuntimeException("");
			}
		}
		return success;
	}

    public static double getDistanceSquared (Cell c1, Cell c2) {
		double sum = 0; int d = c1.getDimensions ();
		for (int i = 0; i < d; i++)
			sum += square (c1.coords [i] - c2.coords [i]);
		return sum;
	}

    public static double getDistanceSquared (Cell cell, double [] point) {
        double sum = 0; int d = cell.getDimensions ();
        for (int i = 0; i < d; i++)
            sum += square (cell.coords [i] - point [i]);
        return sum;
    }

    public static double getDistance (Cell c1, Cell c2) {
		return Math.sqrt (getDistanceSquared (c1, c2));
	}

    public static double getDistance (Cell cell, double [] point) {
        return Math.sqrt (getDistanceSquared (cell, point));
    }

	public static double[] getSegment(double[] coords1, double[] coords2) {
        int d = coords1.length;
        double seg[] = new double[d];
        for (int i = 0; i < d; i++) {
            seg[i] = coords2[i] - coords1[i];
        }
	    return seg;
	}

    public static double sumOfRadii(Cell c1, Cell c2) {
        int d = c1.getDimensions();
        double seg[] = getSegment(c1.getCoords(), c2.getCoords());

        return radius(d, c1.getSize(), seg) + radius(d, c2.getSize(), seg);
    }

    public static double radius(Cell cell, double[] point) {
        int d = cell.getDimensions();
        return radius(d, cell.getSize(), getSegment(cell.getCoords(), point));
    }

    /**
     * 
     * @param d - dimensions
     * @param cellSize - size of cell to get the radius of
     * @param segment - line through the center of the cell
     * @return
     */
    public static double radius(int d, double[] cellSize, double[] segment) {
        double sum = 0;
        for (int i = 0; i < d; i++) {
            sum += cellSize[i];
        }
        if (sum == 0)
            return 0;
        double t = Double.MAX_VALUE;
        for (int i = 0; i < d; i++) {
            t = Math.min(t, Math.abs(cellSize[i] / segment[i]));
        }
        double lengthSquared = 0;
        for (int i = 0; i < d; i++) {
            lengthSquared += square(t * segment[i]);
        }
        return Math.sqrt(lengthSquared) / 2;
    }

    public Rectangle getBounds() {
        return new Rectangle((int) min[0], (int) min[1], (int) size[0], (int) size[1]);
    }

    public void setBounds(Rectangle bounds) {
		min [0] = bounds.x;
		min [1] = bounds.y;
		max [0] = bounds.x + bounds.width;
		max [1] = bounds.y + bounds.height;
		recomputeSize ();
		coords [0] = min[0] + size [0] /2;
		coords [1] = min[1] + size [1] /2;
    }

    public void setCenter(Point defaultPos) {
    	coords [0] = defaultPos.x;
    	coords [1] = defaultPos.y;
    	recomputeBoundaries();
    }
    
}